[chapter].tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import clsx from "clsx";
  2. import { GetServerSideProps } from "next";
  3. import Link from "next/link";
  4. import { useRouter } from "next/router";
  5. import { ReactElement, useState } from "react";
  6. import Footer from "../../../components/common/Footer";
  7. import Header from "../../../components/common/Header";
  8. import NovelCover from "../../../components/NovelCover";
  9. import styles from "../../../styles/chapter.module.scss";
  10. import { ChapterData, ChapterListData } from "../../../types/http";
  11. import useGet from "../../../utils/hooks/useGet";
  12. import { get } from "../../../utils/http";
  13. import type { NextPageWithLayout } from "../../_app";
  14. import toggleTheme from "../../../libs/toggleTheme";
  15. import Settings from "../../../components/novel/Settings";
  16. import Toolbar from "../../../components/novel/Toolbar";
  17. import Toc from "../../../components/novel/Toc";
  18. const Chapter: NextPageWithLayout = () => {
  19. const { query } = useRouter();
  20. const { data: chapterData = null } = useGet<ChapterData>(
  21. `/api/novel/chapter/${query.slug}/${query.chapter}`
  22. );
  23. // const { data: { data: chapterData } = { data: null } } = useGet<ChapterData>(
  24. // `/api/novel/chapter/${query.slug}/${query.chapter}`
  25. // );
  26. const [open, setOpen] = useState(false);
  27. const [menu, setMenu] = useState("");
  28. const [isSerif, setIsSerif] = useState(
  29. JSON.parse(
  30. typeof localStorage !== "undefined" &&
  31. localStorage.getItem("novelIsSerif")
  32. ? localStorage.getItem("novelIsSerif") || "false"
  33. : "false"
  34. ) as boolean
  35. );
  36. const [fontSize, setFontSize] = useState(
  37. Number(
  38. typeof localStorage !== "undefined" &&
  39. localStorage.getItem("novelFontSize")
  40. ? localStorage.getItem("novelFontSize")
  41. : "3"
  42. )
  43. );
  44. const handleOpenToolbar = () => {
  45. if (open) {
  46. setOpen(false);
  47. setMenu("");
  48. } else {
  49. setOpen(true);
  50. }
  51. };
  52. const handleChangeSettings = (type: string = "") => {
  53. if (menu === type) {
  54. setMenu("");
  55. } else {
  56. setOpen(true);
  57. setMenu(type);
  58. }
  59. };
  60. const handleStopPropagation = (e: React.MouseEvent) => {
  61. e.stopPropagation();
  62. };
  63. const handleSetIsSerif = (isSerif: boolean) => {
  64. localStorage.setItem("novelIsSerif", JSON.stringify(isSerif));
  65. setIsSerif(isSerif);
  66. };
  67. const handleSetFontSize = (size: number) => {
  68. localStorage.setItem("novelFontSize", `${size}`);
  69. setFontSize(size);
  70. };
  71. if (!chapterData) {
  72. return null;
  73. }
  74. return (
  75. <>
  76. <header
  77. className={clsx("header", styles["chapter-header"], {
  78. [styles["open"]]: open,
  79. })}
  80. >
  81. <Link href="/" className="logo mr-2" title="NovelDit">
  82. {/* eslint-disable-next-line @next/next/no-img-element */}
  83. <img src="/logo.svg" alt="NovelDit" />
  84. </Link>
  85. <div className="flex-1">
  86. <div className="text-base breadcrumbs">
  87. <ul>
  88. <li>
  89. <Link href={`/novel/${query.slug}`}>{chapterData.title}</Link>
  90. </li>
  91. <li>{chapterData.chapter}</li>
  92. </ul>
  93. </div>
  94. </div>
  95. {/* <div className="buttons">
  96. <button className="btn" onClick={() => toggleTheme()}>
  97. <svg className="icon-sun" viewBox="0 0 16 16">
  98. <use href="/icons.svg#sun"></use>
  99. </svg>
  100. <svg className="icon-moon" viewBox="0 0 16 16">
  101. <use href="/icons.svg#moon"></use>
  102. </svg>
  103. </button>
  104. </div> */}
  105. </header>
  106. <main className={styles["chapter-main"]} onClick={handleOpenToolbar}>
  107. <div
  108. className={clsx(styles["chapter-page"], {
  109. [styles["serif"]]: isSerif,
  110. "text-sm": fontSize === 1,
  111. "text-base": fontSize === 2,
  112. "text-lg": fontSize === 3,
  113. "text-xl": fontSize === 4,
  114. "text-2xl": fontSize === 5,
  115. })}
  116. >
  117. <article className={styles["novel"]}>
  118. <header>
  119. <h1 className="">{chapterData.title}</h1>
  120. <h2 className="">{chapterData.chapter}</h2>
  121. </header>
  122. <div
  123. className="content"
  124. dangerouslySetInnerHTML={{ __html: chapterData.content }}
  125. />
  126. </article>
  127. </div>
  128. <div
  129. className={clsx(styles["toolbar-display"], {
  130. hidden: !menu,
  131. })}
  132. onClick={handleStopPropagation}
  133. >
  134. {menu === "settings" ? (
  135. <Settings
  136. isSerif={isSerif}
  137. fontSize={fontSize}
  138. changeIsSerif={handleSetIsSerif}
  139. changeFontSize={handleSetFontSize}
  140. />
  141. ) : null}
  142. {menu === "toc" ? (
  143. <Toc
  144. novel={query.slug as string}
  145. chapter={query.chapter as string}
  146. />
  147. ) : null}
  148. </div>
  149. </main>
  150. <Toolbar open={open} onChangeSettings={handleChangeSettings} />
  151. </>
  152. );
  153. };
  154. Chapter.getLayout = (page: ReactElement) => page;
  155. export const getServerSideProps: GetServerSideProps<
  156. { fallback: { [key: string]: any } },
  157. { slug: string; chapter: string }
  158. > = async (context) => {
  159. if (!context.params) {
  160. return {
  161. props: {
  162. fallback: {},
  163. },
  164. };
  165. }
  166. const { slug, chapter } = context.params;
  167. const [chapterData, chapters] = await Promise.all([
  168. get<ChapterData>(
  169. `https://novels.yergoo.com/api/novel/chapter/${slug}/${chapter}`
  170. ),
  171. get<ChapterListData>(
  172. `https://novels.yergoo.com/api/novel/${slug}/chapters`
  173. ),
  174. ]);
  175. return {
  176. props: {
  177. fallback: {
  178. [`/api/novel/chapter/${slug}/${chapter}`]: chapterData,
  179. [`/api/novel/${slug}/chapters`]: chapters,
  180. },
  181. },
  182. };
  183. };
  184. export default Chapter;